# Quartz API
- Scheduler - 与调度程序交互的主要 API。
- Job - 由希望由调度程序执行的组件实现的接口。
- JobDetail - 用于定义作业的实例。
- Trigger(即触发器) - 定义执行给定作业的计划的组件。
- JobBuilder - 用于定义/构建 JobDetail 实例,用于定义作业的实例。
- TriggerBuilder - 用于定义/构建触发器实例。
# Job 与 JobDetail
你定义了一个实现 Job 接口的类,这个类仅仅表明该 job 需要完成什么类型的任务,除此之外,Quartz 还需要知道该 Job 实例所包含的属性;这将由 JobDetail 类来完成。
public class HelloJob implements Job {
public HelloJob() { }
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println("Hello! HelloJob is executing.");
}
}
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这里有个问题就是我需要在 HelloJob 类定义几个属性,用于数据处理,但是这里每次当 scheduler 执行 job 时,在调用 execute(…)方法之前会创建该类的一个新的实例,原来的属性的值就用不到了,这里 quartz 给我们提供了 JobDataMap 用作处理
# JobDataMap
JobDataMap 中可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据;JobDataMap 是 Java Map 接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
JobDetail job = newJob(DumbJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();
public class DumbJob implements Job {
public DumbJob() { }
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
JobExecutionContext 中的 JobDataMap 为我们提供了很多的便利。它是 JobDetail 中的 JobDataMap 和 Trigger 中的 JobDataMap 的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getMergedJobDataMap();
2
也可以实现自动注入,只需在实现 Job 接口的类中添加属性及其 getter,setter 方法即可
# Job 实例
你可以只创建一个 job 类的实现,然后创建多个与该 job 关联的 JobDetail 实例,每一个实例都有自己的属性集和 JobDataMap,最后,将所有的实例都加到 scheduler 中。
# Job 状态与并发
- @DisallowConcurrentExecution:将该注解加到 job 类上,告诉 Quartz 不要并发地执行同一个 job 定义(这里指特定的 job 类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对 JobDetail 的,而不是 job 类的。但是我们认为(在设计 Quartz 的时候)应该将该注解放在 job 类上,因为 job 类的改变经常会导致其行为发生变化。
- @PersistJobDataAfterExecution:将该注解加在 job 类上,告诉 Quartz 在成功执行了 job 类的 execute 方法后(没有发生任何异常),更新 JobDetail 中 JobDataMap 的数据,使得该 job(即 JobDetail)在下一次执行的时候,JobDataMap 中是更新后的数据,而不是更新前的旧数据。
- @DisallowConcurrentExecution,尽管注解是加在 job 类上的,但其限制作用是针对 job 实例的,而不是 job 类的。由 job 类来承载注解,是因为 job 类的内容经常会影响其行为状态(比如,job 类的 execute 方法需要显式地“理解”其”状态“)。
- 如果你使用了@PersistJobDataAfterExecution 注解,我们强烈建议你同时使用@DisallowConcurrentExecution 注解,因为当同一个 job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap 中存储的数据很可能是不确定的。
# Trigger
触发器,用来设置 job 的触发时间,频次的。主要有两种 Trigger:Simple Trigger,Cron Trigger
# Simple Trigger
释义:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次
特点:开始时间、结束时间、重复次数以及重复的间隔
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.forJob(myJob) // identify job with handle to its JobDetail itself
.build();
2
3
4
5
6
7
8
# Cron Trigger
释义:基于日历的概念,进行指定间隔进行重新启动的作业启动计划
特点:cron 表达式
字段名 | 允许的值 | 允许的特殊字符 |
---|---|---|
Seconds(秒) | 0-59 | , - * / |
Minutes(分) | 0-59 | , - * / |
Hours(小时) | 0-23 | , - * / |
Day-of-Month | (日) 1-31 | , - * ? / L W C |
Month(月 ) | 1-12 or JAN-DEC | , - * / |
Day-of-Week(周几) | 1-7 or SUN-SAT | , - * ? / L C # |
Year (年(可选字段)) | empty 1970-2099 | , - * / |
特殊字符含义:
-
:代表所有可能的值。因此,“*”在 Month 中表示每个月,在 Day-of-Month 中表示每天,在 Hours 表示每小时
*
:表示指定范围。
,
:表示列出枚举值。例如:在 Minutes 子表达式中,“5,20”表示在 5 分钟和 20 分钟触发。
/
:被用于指定增量。例如:在 Minutes 子表达式中,“0/15”表示从 0 分钟开始,每 15 分钟执行一次。"3/20"表示从第三分钟开始,每 20 分钟执行一次。和"3,23,43"(表示第 3,23,43 分钟触发)的含义一样。
?
:用在 Day-of-Month 和 Day-of-Week 中,指“没有具体的值”。当两个子表达式其中一个被指定了值以后,为了避免冲突,需要将另外一个的值设为“?”。例如:想在每月 20 日触发调度,不管 20 号是星期几,只能用如下写法:0 0 0 20 _ ?,其中最后以为只能用“?”,而不能用“_”。
L
:用在 day-of-month 和 day-of-week 字串中。它是单词“last”的缩写。它在两个子表达式中的含义是不同的。
在 day-of-month 中,“L”表示一个月的最后一天,一月 31 号,3 月 30 号。
在 day-of-week 中,“L”表示一个星期的最后一天,也就是“7”或者“SAT”
如果“L”前有具体内容,它就有其他的含义了。例如:“6L”表示这个月的倒数第六天。“FRIL”表示这个月的最后一个星期五。
注意:在使用“L”参数时,不要指定列表或者范围,这样会出现问题。
W
:“Weekday”的缩写。只能用在 day-of-month 字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在 day-of-month 字段用“15W”指“最接近这个月第 15 天的工作日”,即如果这个月第 15 天是周六,那么触发器将会在这个月第 14 天即周五触发;如果这个月第 15 天是周日,那么触发器将会在这个月第 16 天即周一触发;如果这个月第 15 天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在 day-of-month 指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日,即最后一个星期五。
#
:只能用在 day-of-week 字段。用来指定这个月的第几个周几。例:在 day-of-week 字段用"6#3" or "FRI#3"指这个月第 3 个周五(6 指周五,3 指第 3 个)。如果指定的日期不存在,触发器就不会触发。
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 \* \* ?"))
.forJob("myJob", "group1")
.build();
2
3
4
5
# TriggerListeners 和 JobListeners 和 SchedulerListeners
TriggerListeners 接收到与触发器(trigger)相关的事件,JobListeners 接收与 jobs 相关的事件。与触发相关的事件包括:触发器触发,触发失灵,并触发完成(触发器关闭的作业完成)。
SchedulerListeners 接收与 Scheduler 相关的事件。与计划程序相关的事件包括:添加 job/触发器,删除 job/触发器,调度程序中的严重错误,关闭调度程序的通知等
# Job Stores
JobStore 负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers,日历等
# RAMJobStore
RAMJobStore 是使用最简单的 JobStore,它也是性能最高的(在 CPU 时间方面)。RAMJobStore 以其明显的方式获取其名称:它将其所有数据保存在 RAM 中。这就是为什么它是闪电般快的,也是为什么这么简单的配置。缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着 RAMJobStore 无法履行作业和 triggers 上的“非易失性”设置。对于某些应用程序,这是可以接受的 - 甚至是所需的行为,但对于其他应用程序,这可能是灾难性的。
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# JDBC JobStore
JDBCJobStore 也被恰当地命名 - 它通过 JDBC 将其所有数据保存在数据库中。因此,配置比 RAMJobStore 要复杂一点,而且也不是那么快。但是,性能下降并不是很糟糕,特别是如果您在主键上构建具有索引的数据库表。
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
2
注意表结构可以参考这个https://github.com/quartz-scheduler/quartz/tree/quartz-2.1.7/docs/dbTables